<?PHP
/**
 * OpenStation
 *
 * @Application : Session.class.php
 * @Encode : 65001 Unicode ( UTF-8 NO BOM )
 * @Description : Session 封装类
 * @Author : HK <hkyyang@gmail.com>
 * @Version : 1.5.0
 */

class Session {

    static private $session_started = FALSE;

    // 押入数组末尾(入栈)通配符
    static private $_auto = '*';
    // 赋值运算模板
    static private $operators = array(
        // Numeric
        '+=' => '+= $value',
        '-=' => '-= $value',
        '=-' => '= $value - $last_value',
        '*=' => '*= $value',
        '/=' => '= $last_value == 0 ? 0 : ( $value != 0 ? $last_value / $value : $last_value )',
        '=/' => '= $value == 0 ? 0 : ( $last_value != 0 ? $value / $last_value : $value )',
        '%=' => '= $value == 0 ? 0 : $last_value % $value',
        // String
        '.=' => '.= $value',
        '=.' => '= $value . $last_value'
    );

    // 只读状态，不更新活动时间
    static private $readonly = FALSE;


    /**
    * start 方法，初始化一个Session会话
    *
    * @static
    * @access public
    * @param $
    * @return Void
    */
    static public function start() {

        // 第一次开启会话
        if( ! self :: $session_started ) {
            ini_set( 'session.auto_start', 0 );
            session_start();
            // 标记会话已经开启
            self :: $session_started = TRUE;

            // 设置当前项目 SESSION 目录，如果配置中指定了SESSION 目录
            if( Cache :: get( '@.SESSION.APP_SAVE_PATH' ) ) {
                self :: path( Cache :: get( 'APP_PATH.SESSION' ) );
            }

            // 检查一个已经存在的SESSION文件最大生命时间
            if( Cache :: get( '@.SESSION.LIFE_TIME' ) ) {
                $file = self :: get_session_file();
                if( is_file( $file ) ) {
                    if( ( time() - filectime( $file ) ) >= Cache :: get( '@.SESSION.LIFE_TIME' ) ) {
                        rm( $file );
                    }
                }
            }
        }

        // 当SESSION 会话第一次创建时
        if( ! isset( $_SESSION[ 'OS_HTTP_SESSION' ][ 'ACTIVE' ] ) ) {
            // 第一次创建会话时间记录
            $_SESSION[ 'OS_HTTP_SESSION' ][ 'ACTIVE' ] = time();

        // 当 SESSION 再开
        } else {

            // 如果没有超时，检查SESSION空闲时间
            if( ! isset( $_SESSION[ 'OS_HTTP_SESSION' ][ 'TIMEOUT' ] ) ) {
                // 当配置中设置了最大空闲时间
                if( Cache :: get( '@.SESSION.IDLE_TIME' ) ) {
                    // 检查最后一次活动时间到目前时间差
                    if( ( time() - $_SESSION[ 'OS_HTTP_SESSION' ][ 'ACTIVE' ] ) >= Cache :: get( '@.SESSION.IDLE_TIME' ) ) {
                        // 标记此会话超时
                        $_SESSION[ 'OS_HTTP_SESSION' ][ 'TIMEOUT' ] = TRUE;
                    }
                }
            }

            // 更新活动时间
            if( ! isset( $_SESSION[ 'OS_HTTP_SESSION' ][ 'TIMEOUT' ] ) && ! self :: $readonly ) {
                $_SESSION[ 'OS_HTTP_SESSION' ][ 'ACTIVE' ] = time();
            }
            self :: $readonly = FALSE;
        }
    }

    /**
     * set 方法，设置SESSION
     * Array 不被处理为普通非标量，而是算做多维赋值
     * 键名必须以"." 构造，支持无限级构造
     * 支持任何类型及非标量作为值
     * 运算符只在值为标量(除Boolean)并且有原值时有效, 否则会产生不可预料的结果
     *
     * @static
     * @access public
     * @param String | Array : $name, 变量名称、键名构造或配置数组
     * @param Mixed : $value, 变量值
     * @param String | Null : $operator, 运算符, 必须为赋值运算
     * @return Mixed
     */
    static public function set( $name, $value = NULL, $operator = NULL ) {

        self :: start();

        if( empty( $name ) || ( ! is_scalar( $name ) && ! is_array( $name ) ) || is_bool( $name ) ) {
            return NULL;
        }

        // 键名统一大写
        if( is_array( $name ) ) {
            $name = Cache :: key_uppercase_recursive( $name );
        } else {
            $name = strtoupper( $name );
        }
        // 值的键名大写
        if( is_array( $value ) && count( $value ) ) {
            $value = Cache :: key_uppercase_recursive( $value );
        }

        // 参数一为数组时，忽略参数二
        if( is_array( $name ) ) {
            return $_SESSION = array_merge( $_SESSION, $name );
        }

        $_temp = explode( '.', $name );
        $_key = '';
        // 构造配置多维数组
        for( $i = 0, $n = count( $_temp ); $i < $n; $i ++ ) {
            // 自增数组
            if( $_temp[ $i ] == self :: $_auto ) {
                $_key .= "[]";
            } else {
                $_key .= "['" . $_temp[$i] . "']";
            }
        }
        // 当有运算符并有上次赋值时
        if( array_key_exists( $operator, self :: $operators ) ) {
            $last_value = eval( "return isset( \$_SESSION$_key ) ? \$_SESSION$_key : NULL;" );
            if( ! is_null( $last_value ) ) {
                $oper = self :: $operators[ $operator ];
                return eval( "return \$_SESSION$_key $oper;" );
            }
        }
        return eval( "return \$_SESSION$_key = \$value;" );

    }

    /**
     * readonly 方法，获取变量值，只读，不更新活动时间
     *
     * @static
     * @access public
     * @param String : $name, 变量名称或键名构造
     * @return Mixed
     */
    static public function readonly( $name = NULL ) {
        self :: $readonly = TRUE;
        return self :: get( $name );
    }

    /**
     * get 方法，获取变量值
     *
     * @static
     * @access public
     * @param String : $name, 变量名称或键名构造
     * @return Mixed
     */
    static public function get( $name = NULL ) {

        self :: start();

        if( is_null( $name ) ) {
            return  $_SESSION;
        }
        $_temp = explode( '.', $name );
        $_key = '';
        for( $i = 0, $n = count( $_temp ); $i < $n; $i ++ ) {
            $_key .= "['" . $_temp[$i] . "']";
        }
        return eval( "return isset( \$_SESSION$_key ) ?  \$_SESSION$_key : NULL;" );
    }

    /**
     * copy 方法，内部复制并再计算赋值
     * 如果尝试把自己复制到自己的子集，将不会有操作并返回NULL
     *
     * @static
     * @access public
     * @param String : $name, 被复制键名构造字符串
     * @param String : $target, 目标键名构造字符串
     * @param String : $value, 对复制的值进行额外赋值运算的值
     * @param String : $operators, 赋值计算
     * @return NULL | String
     */
    static public function copy( $name, $target, $value = NULL, $operator = NULL ) {
        $last_value = self :: get( $name );
        if( is_null( $last_value ) || self :: parent( $target ) == strtoupper( $name ) ) {
            return NULL;
        }
        if( ! is_null( $value ) && ! is_null( $operator ) ) {
            self :: set( $target, $last_value );
            $last_value = self :: set( $target, $value, $operator );
        } else {
            self :: set( $target, $last_value );
        }
        return $last_value;
    }

    /**
     * find 方法，尝试用一个值来搜索索引构造字符串
     *
     * @static
     * @access public
     * @param $value 值, 可以是任何类型
     * @param String, $parent 键名构造字符串,指定在这个范围内搜索
     * @param Boolean : Default False, $strict 严格匹配，包括类型
     * @return NULL | String
     */
    static public function find( $value, $parent = NULL, $strict = false ) {

        if( is_null( self :: get( $parent ) ) || is_null( $value ) ) {
            return NULL;
        }
        $c = self :: get( $parent );
        if( is_array( $c ) ) {
            foreach( $c as $k => $v ) {
                // 搜索的值是一个数组
                if( is_array( $value ) && $c == $value ) {
                    return $parent;
                }
                $key = $parent ? $parent . '.' . $k : $k;
                $tmp = self :: find( $value, $key, $strict );
                if( is_null( $tmp ) ) {
                    continue;
                }
                return $tmp;
            }
        } else {
            if( $strict == true && $value === $c ) {
                return $parent;
            } else if( $value == $c ) {
                return $parent;
            }
        }
        return NULL;
    }

    /**
     * out 方法，取出(删除)任意位置的一个元素
     *
     * @static
     * @access public
     * @param String : $name, 键名构造字符串
     * @return NULL | Mixed
     */
    static public function out( $name = NULL ) {
        $value = self :: get( $name );
        if( ! is_null( $value ) ) {
            if( ! empty( $name ) ) {
                $_temp = explode( '.', strtoupper( $name ) );
                $_key = '';
                for( $i = 0, $n = count( $_temp ); $i < $n; $i ++ ) {
                    $_key .= "['" . $_temp[$i] . "']";
                }
                eval( "unset( \$_SESSION$_key );" );
            } else {
                $_SESSION = array();
                session_destroy();
            }
        }
        return $value;
    }

    /**
     * sfhit 方法，取出第一个元素
     *
     * @static
     * @access public
     * @param String : $name, 键名构造字符串
     * @return NULL | Mixed
     */
    static public function shift( $name = NULL ) {
        $value = self :: get( $name );
        if( is_null( $value ) ) {
            return $value;
        } else if( is_array( $value ) ) {
            $get = array_shift( $value );
            self :: set( $name, $value );
            return $get;
        }
        return NULL;
    }

    /**
     * pop 方法，取出最后一个元素
     *
     * @static
     * @access public
     * @param String, $name 键名构造字符串
     * @return NULL | Mixed
     */
    static public function pop( $name = NULL ) {
        $value = self :: get( $name );
        if( is_null( $value ) ) {
            return $value;
        } else if( is_array( $value ) ) {
            $get = array_pop( $value );
            self :: set( $name, $value );
            return $get;
        }
        return NULL;
    }

    /**
     * append 方法，追加插入(入栈)
     * 非数组会被强制转换为数组，原值作为第一个元素
     *
     * @static
     * @access public
     * @param String : $name, 键名构造字符串
     * @param Mixed : $value, 值
     * @return NULL | Mixed
     */
    static public function append( $name = NULL, $value = NULL ) {
        $old = self :: get( $name );
        if( is_null( $old ) || is_array( $old ) ) {
            return self :: set( $name . '.*', $value );
        } else {
            return self :: set( $name, array( $old, $value ) );
        }
    }

    /**
     * prepend 方法，前置插入
     * 非数组会被强制转换为数组，原值作为末尾元素
     *
     * @static
     * @access public
     * @param String : $name, 键名构造字符串
     * @param Mixed : $value, 值
     * @return NULL | Mixed
     */
    static public function prepend( $name = NULL, $value = NULL ) {
        $old = self :: get( $name );
        if( is_null( $old ) || is_array( $old ) ) {
            array_unshift( $old, $value );
            return self :: set( $name, $old );
        } else {
            return self :: set( $name, array( $value, $old ) );
        }
    }

    /**
     * parent 方法，获取键名构造的父级构造
     *
     * @static
     * @access public
     * @param String : $name, 键名构造字符串
     * @param Boolean : $root, 获取顶级的父键名
     * @return NULL | Mixed
     */
    static public function parent( $name, $root = FALSE ) {
        $_temp = explode( '.', strtoupper(  $name ) );
        if( $root ) {
            $name = $_temp[ 0 ];
        } else {
            array_pop( $_temp );
            $name = implode( '.', $_temp );
        }
        if( $name === '' ) {
            $name = NULL;
        }

        return $name;
    }

    /**
     * type 方法，获取类型或判断类型，如果没有值的话，返回NULL
     *
     * @static
     * @access public
     * @param String : $name, 键名构造字符串
     * @param String : $is_type, 判断是否此类型，可选值 string | integer | float | boolean | array | object | resource
     * @return NULL | String | Boolean
     */
    static public function type( $name, $is_type = NULL ) {

        $value = self :: get( $name );
        if( is_null( $value ) ) {
            return NULL;
        }

        $type = NULL;

        switch( is_scalar( $value ) ) {
            // 正常标量
            case TRUE :
                switch( TRUE ) {
                    case is_string( $value ) : $type = 'string'; break;
                    case is_int( $value ) : $type = 'integer'; break;
                    case is_bool( $value ) : $type = 'boolean'; break;
                    case is_float( $value ) : $type = 'float'; break;
                }
                break;
            // 非标量
            case FALSE :
                switch( TRUE ) {
                    case is_array( $value ) : $type = 'array'; break;
                    case is_object( $value ) : $type = 'object'; break;
                    case is_resource( $value ) : $type = 'resource'; break;
                }
                break;
        }

        // 判断类型模式，返回Boolean值
        if( ! is_null( $type ) && ! is_null( $is_type ) ) {
            if( $type == strtolower( $is_type ) ) {
                return TRUE;
            }
            return FALSE;
        }

        return $type;

    }

    /**
     * timeout 方法，检查Session会话是否超时
     *
     * @static
     * @access public
     * @param Boolean : $stop, 结束删除会话
     * @return Boolean
     */
    static public function timeout( $stop = FALSE ) {
        $timeout = self :: get( 'OS_HTTP_SESSION.TIMEOUT' ) ? TRUE : FALSE;
        if( $timeout && $stop ) {
            self :: stop();
        }
        return $timeout;
    }

    /**
     * stop 方法，结束删除Session
     *
     * @static
     * @access public
     * @param $
     * @return void
     */
    static public function stop() {
        Session :: start();
        $_SESSION = array();
        if ( isset( $_COOKIE[ session_name() ] ) ) {
            setcookie( session_name(), '', time() - 3600, '/' );
        }
        session_destroy();
        // 删除session临时文件
        rm( self :: get_session_file() );
    }





    /**
     * path 方法，设置或获取 session 路径
     *
     * @static
     * @access private
     * @param String | NULL, 设置 | 获取
     * @return String
     */
    static private function path( $path = NULL ) {
        if( ! empty( $path ) ) {
            session_save_path( $path );
        } else {
            $path = session_save_path();
            $path = path_replace( $path );
            $path = string_append_replace( $path, DS );
            return $path;
        }
    }

    /**
     * getID 方法，获取当前会话ID
     *
     * @static
     * @access public
     * @param $
     * @return String
     */
    static public function getID() {
        self :: start();
        return session_id();
    }

     /**
      * get_session_file方法，返回当前session文件路径
      *
      * @static
      * @access private
      * @param $
      * @return String
      */
    static public function get_session_file() {
        return self :: path() . 'sess_'.session_id();
    }

}

?>